#ifndef INCLUDE_GUARD_XE2CLQBZSOWG0KPR5F6HTV8YDMIU37A49N
#define INCLUDE_GUARD_XE2CLQBZSOWG0KPR5F6HTV8YDMIU37A49N

/*
 * array_ref.hpp
 * This file is part of norxx
 *
 * Copyright (C) 2014 - Sebastian Gesemann
 *
 * norxx is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * norxx is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with norxx. If not, see <http://www.gnu.org/licenses/>.
 */

#include <cassert>
#include <cstddef>
#include <vector>
#include <array>
#include <type_traits>

namespace util {

template<class T, class U>
struct enable_if_safely_convertible_array_element
: std::enable_if<
	std::is_convertible<T*,U*>::value && (sizeof(T) == sizeof(U))
> {};

/// reference-like proxy for a linear array with some extra goodies
template<class T>
class array_ref
{
public:
	array_ref(std::nullptr_t = nullptr)
	: beg(nullptr), siz(0) {}

	array_ref(T* base, std::size_t size)
	: beg(base), siz(size) {}

	template<class U
		,class = typename enable_if_safely_convertible_array_element<U,T>::type
	>
	array_ref(array_ref<U> const& x)
	: beg(x.beg), siz(x.siz) {}

	template<class U, std::size_t N
		,class = typename enable_if_safely_convertible_array_element<U,T>::type
	>
	array_ref(U (&array_ref)[N])
	: beg(array_ref), siz(N) {}

	template<class U, std::size_t N
		,class = typename enable_if_safely_convertible_array_element<const U,T>::type
	>
	array_ref(std::array<U,N> const& array_ref)
	: beg(N>0 ? &array_ref[0] : nullptr), siz(N) {}

	template<class U, std::size_t N
		,class = typename enable_if_safely_convertible_array_element<U,T>::type
	>
	array_ref(std::array<U,N>& array_ref)
	: beg(N>0 ? &array_ref[0] : nullptr), siz(N) {}

	template<class U, class Alloc
		,class = typename enable_if_safely_convertible_array_element<const U,T>::type
	>
	array_ref(std::vector<U,Alloc> const& vec)
	: beg(vec.empty() ? nullptr : &vec[0]), siz(vec.size()) {}

	template<class U, class Alloc
		,class = typename enable_if_safely_convertible_array_element<U,T>::type
	>
	array_ref(std::vector<U,Alloc> & vec)
	: beg(vec.empty() ? nullptr : &vec[0]), siz(vec.size()) {}

	T* base() const { return beg; }
	std::size_t size() const { return siz; }
	bool empty() const { return siz==0; }
	bool operator!() const { return siz==0; }

	T      *  begin() const { return beg; }
	T const* cbegin() const { return beg; }
	T      *  end()   const { return beg+siz; }
	T const* cend()   const { return beg+siz; }

	T& operator[](std::size_t index) const
	{
		assert(index < siz);
		return beg[index];
	}

	/// returns a reference to the first element and "consumes" it
	/// in the sense that the array_ref objects is modified to exclude it.
	T& consume_first()
	{
		assert(siz>0);
		--siz;
		return *beg++;
	}

	/// returns a reference to the last element and "consumes" it
	/// in the sense that the array_ref objects is modified to exclude it.
	T& consume_last()
	{
		assert(siz>0);
		--siz;
		return beg[siz];
	}

	/// returns a reference to the first n elements and "consumes" them
	/// in the sense that the array_ref objects is modified to exclude them.
	array_ref<T> consume_first_n(std::size_t n)
	{
		assert(siz>=n);
		array_ref<T> result (beg,n);
		beg += n;
		siz -= n;
		return result;
	}

	/// returns a reference to the last n elements and "consumes" them
	/// in the sense that the array_ref objects is modified to exclude them.
	array_ref<T> consume_last_n(std::size_t n)
	{
		assert(siz>=n);
		array_ref<T> result (beg+(siz-n),n);
		siz -= n;
		return result;
	}

private:
	T* beg;
	std::size_t siz;

	template<class> friend class array_ref;
};

/// returns an array_ref object that refers to the first n elements
template<class T>
array_ref<T> first(array_ref<T> const& x, std::size_t count)
{
	assert(count <= x.size());
	return array_ref<T>(x.base(),count);
}

/// returns an array_ref object that refers to the last n elements
template<class T>
array_ref<T> last(array_ref<T> const& x, std::size_t count)
{
	assert(count <= x.size());
	return array_ref<T>(x.base()+(x.size()-count),count);
}

/// returns an array_ref object that refers to some portion of
/// the given array starting at 'start' and counting 'count' elements.
template<class T>
array_ref<T> crop(array_ref<T> const& x, std::size_t start, std::size_t count)
{
	assert(start <= x.size());
	assert(count <= x.size());
	assert(start+count <= x.size());
	return array_ref<T>(x.base()+start,count);
}

} // namespace util

#endif

